package com.bytz.modules.cms.common.service.imp;

import cn.hutool.core.map.MapUtil;
import com.bytz.common.util.RedisUtil;
import com.bytz.modules.cms.common.constants.CmsConstant;
import com.bytz.modules.cms.common.model.CategoryDiscountSearchVo;
import com.bytz.modules.cms.common.service.CmsCacheService;
import com.bytz.modules.cms.common.util.CmsCacheKeyUtil;
import com.bytz.modules.cms.product.entity.ProductDiscountCategoryLevel;
import com.bytz.modules.cms.product.service.IProductDiscountCategoryLevelService;
import com.bytz.modules.cms.productLine.entity.ProductLineLevel;
import com.bytz.modules.cms.productLine.service.IProductLineLevelService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;


@Service
public class CmsCacheServiceImpl implements CmsCacheService {

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private IProductLineLevelService productLineLevelService;

    @Autowired
    private IProductDiscountCategoryLevelService productDiscountCategoryLevelService;

    @Override
    public Map<String, ProductLineLevel> getProductLineLevel(List<String> ids) {
        // 计算缓存Key
        List<String> cacheKeys = ids.stream()
                .map(p -> CmsCacheKeyUtil.getProductLineLevelKey(CmsConstant.CachePrefixes.PRODUCT_LINE_LEVEL, p))
                .collect(Collectors.toList());

        List<ProductLineLevel> fromCaches = redisUtil.multiGet(cacheKeys, ProductLineLevel.class);

        // 查缓存中没有的并且合并结果返回
        return unionCacheAndDb(fromCaches, ids,
                fromCache -> fromCache.getId() != null,
                noCacheIds -> productLineLevelService.getByIds(noCacheIds)
                        .stream().collect(Collectors.toMap(ProductLineLevel::getId, v -> v)),
                ProductLineLevel::new,
                noCacheId -> CmsCacheKeyUtil.getProductLineLevelKey(CmsConstant.CachePrefixes.PRODUCT_LINE_LEVEL, noCacheId));
    }

    @Override
    public Map<CategoryDiscountSearchVo, ProductDiscountCategoryLevel> getCategoryDiscounts(
            List<CategoryDiscountSearchVo> searchVos) {
        List<String> cacheKeys = searchVos.stream()
                .map(searchVo -> CmsCacheKeyUtil.getProductCategoryDiscountKey(
                        CmsConstant.CachePrefixes.PRODUCT_CATEGORY_DISCOUNT, searchVo))
                .collect(Collectors.toList());

        List<ProductDiscountCategoryLevel> fromCaches = redisUtil.multiGet(cacheKeys, ProductDiscountCategoryLevel.class);

        // 查缓存中没有的并且合并结果返回
        return unionCacheAndDb(fromCaches, searchVos,
                fromCache -> fromCache.getId() != null,
                noCacheKeys -> productDiscountCategoryLevelService.getByKeys(noCacheKeys).stream()
                        .collect(Collectors.toMap(k -> new CategoryDiscountSearchVo(k.getDiscountCategoryId(), k.getProductLineLevelId()), v -> v)),
                ProductDiscountCategoryLevel::new,
                noCacheKey -> CmsCacheKeyUtil.getProductCategoryDiscountKey(CmsConstant.CachePrefixes.PRODUCT_LINE_LEVEL, noCacheKey));
    }

    /**
     * 合并缓存和数据库中的数据，如果缓存都命中则不会查数据库，步骤
     * 1. 先比较出数据库中不存在的id
     * 2. 将存在的放入结果（会判断不是占位对象才放）
     * 3. 从数据库中查不存在的
     * 4. 将数据库中的数据放到缓存中（如果查不到的放缓存一个占位对象）
     * 5. 将查到的对象放到结果返回
     * @param fromCaches 从缓存查到的数据
     * @param keys 要查的全部id
     * @param isValidCacheFunc 是否为有效的缓存，因为了防止缓存穿透，数据库中没有的数据也放一个占位在缓存，这个方法用来判断缓存中的是否为占位
     * @param fetchDataFunc 从数据库中获取数据的方法
     * @param newInstanceFunc 创建点位对象的方法
     * @param cacheKeyGetter 缓存Key生成器
     * @param <T> 类型
     */
    private <T, K> Map<K, T> unionCacheAndDb(List<T> fromCaches,
                                               List<K> keys,
                                               Predicate<T> isValidCacheFunc,
                                               Function<List<K>, Map<K, T>> fetchDataFunc,
                                               Supplier<T> newInstanceFunc,
                                               Function<K, String> cacheKeyGetter) {
        Map<K, T> result = MapUtil.newHashMap(keys.size());
        List<K> noCacheKeys = new ArrayList<>(fromCaches.size());
        for (int i = 0; i < fromCaches.size(); i++) {
            T fromCache = fromCaches.get(i);
            if (fromCache == null) {
                noCacheKeys.add(keys.get(i));
            } else {
                if (isValidCacheFunc.test(fromCache)) {
                    result.put(keys.get(i), fromCache);
                }
            }
        }

        if (noCacheKeys.size() > 0) {
            Map<K, T> getFromDbs = fetchDataFunc.apply(noCacheKeys);
            Map<String, T> saveToCaches = MapUtil.newHashMap(noCacheKeys.size());

            for (K noCacheKey : noCacheKeys) {
                String cacheKey= cacheKeyGetter.apply(noCacheKey);
                T fromDb = getFromDbs.get(noCacheKey);
                if (fromDb == null) {
                    saveToCaches.put(cacheKey, newInstanceFunc.get());
                } else {
                    saveToCaches.put(cacheKey, fromDb);
                    result.put(noCacheKey, fromDb);
                }
            }

            // 保存到缓存
            redisUtil.multiSet(saveToCaches);
        }

        return result;
    }
}
